package com.codebattery.service;

import com.codebattery.Idempotent;
import com.codebattery.util.KeyUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 幂等切面
 * @author wangchao
 */
@Aspect
@Component
@ConditionalOnClass(RedisTemplate.class)
public class IdempotentAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(IdempotentAspect.class);
    /**
     * redis缓存key的模板
     */
    private static final String KEY_TEMPLATE = "idempotent_%s";

    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * 根据实际路径进行调整
     */
    @Pointcut("@annotation(com.codebattery.Idempotent)")
    public void executeIdempotent() {
    }

    @Around("executeIdempotent()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
      	//获取方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
      	//获取幂等注解
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
      	//根据 key前缀 + @Idempotent.value() + 方法签名 + 参数 构建缓存键值
      	//确保幂等处理的操作对象是：同样的 @Idempotent.value() + 方法签名 + 参数
        String key = String.format(KEY_TEMPLATE, idempotent.value() + "_"
                + KeyUtil.generate(method, joinPoint.getArgs()));
        //通过setnx确保只有一个接口能够正常访问
        //调用KeyUtil工具类生成key
      	boolean result = redisTemplate.opsForValue()
                .setIfAbsent(key, "PX", idempotent.expireMillis(), TimeUnit.MILLISECONDS);
      
        if (result) {
            return joinPoint.proceed();
        } else {
            LOGGER.debug("Idempotent hits, key=" + key);
            throw new IdempotentException("Idempotent hits, key=" + key);
        }
    }
}